package edu.pg.ipse.kb.frame.app;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.MapContext;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import edu.pg.ipse.kb.frame.core.Constants;
import edu.pg.ipse.kb.frame.core.Context;
import edu.pg.ipse.kb.frame.core.ResultType;
import edu.pg.ipse.kb.frame.inference.DefaultInferenceEngine;
import edu.pg.ipse.kb.frame.inference.IInferenceEngine;
import edu.pg.ipse.kb.frame.kb.DefaultFrameAccessObject;
import edu.pg.ipse.kb.frame.kb.IFrameAccessObject;
import edu.pg.ipse.utils.ExpressionParser;
import edu.pg.ipse.utils.MathUtils;
import edu.pg.ipse.utils.Utils;

public class Main {

	static {
		// initialize Logger configuration
		try {
			PropertyConfigurator
					.configure("configuration/log4j/log4j.properties");
		} catch (Throwable e) {
			throw new ExceptionInInitializerError();
		}
	}

	private static final Scanner consoleScanner = new Scanner(System.in);

	private static final Logger logger = Logger.getRootLogger();

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		logger.info("Application started");
		
		Utils.printOnScreen("Использовать входные данные из файлов? (y/n): ");
		String decision = consoleScanner.next();
		
		final Double processorsCount;
		final Double arrayLength;
		
		test();
		
		if(decision.toLowerCase().equals("y")) {
			
			Context.getInstance().setParameter(Constants.Parameters.CUSTOM_MODE, new Boolean(true));
			
			// read data
			String speedupExpression = readParameter("speedup");
			Context.getInstance().setParameter(Constants.Parameters.SPEEDUP_EXPR, speedupExpression);
			
			String effExpression = readParameter("efficiency");
			Context.getInstance().setParameter(Constants.Parameters.EFFICIENCY_EXPR, effExpression);
			
			String alphaExpression = readParameter("alpha");
			Context.getInstance().setParameter(Constants.Parameters.ALPA_EXPR, alphaExpression);
			
			String pExpression = readParameter("p");
			
			Utils.printOnScreen("~~~Задача\n");
			Utils.printOnScreen("Описание: 'Определена функционально'\n");
			Utils.printOnScreen("~~~Входные данные\n");
			
			arrayLength = defineArrayLenght(args);

			Utils.printOnScreen("\n~~~Вычислительный модуль\n");
			Utils.printOnScreen("Параллельная реализация: 'Определена функционально'\n");
			Utils.printOnScreen("~~~Вычислительная среда\n");
			Utils.printOnScreen("Aрхитектура: 'Определена функционально'\n");
			
			String pResulMessage = "Kоличество процессоров (p): ";
			if(pExpression == "" || pExpression == null)
			{
				if (args.length > 1) {
					processorsCount = new Double(args[1]);
				} else {
					Utils.printOnScreen("Введите количество процессоров: ");
					processorsCount = consoleScanner.nextDouble();
				}
			} else {
				// evaluate it
				JexlContext jc = new MapContext();
				jc.set("n", arrayLength);
				
				processorsCount = (Double) ExpressionParser.getInstance().evaluate(pExpression, jc);
				pResulMessage += pExpression + " = ";
			}
			pResulMessage += processorsCount.intValue();
			Utils.printOnScreen(pResulMessage + "\n");
			logger.info(pResulMessage);
			
		} else {
			
			Context.getInstance().setParameter(Constants.Parameters.CUSTOM_MODE, new Boolean(false));
			
			Utils.printOnScreen("~~~Задача\n");
			Utils.printOnScreen("Описание: 'Суммирование элементов массива'\n");
			Utils.printOnScreen("~~~Входные данные\n");
			
			arrayLength = defineArrayLenght(args);

			Utils.printOnScreen("\n~~~Вычислительный модуль\n");
			Utils.printOnScreen("Параллельная реализация: 'Модифицированная каскадная схема'\n");
			Utils.printOnScreen("~~~Вычислительная среда\n");
			Utils.printOnScreen("Aрхитектура: 'SMP'\n");
			
			if (args.length > 1) {
				processorsCount = new Double(args[1]);
			} else {
				Utils.printOnScreen("Введите количество процессоров: ");
				processorsCount = consoleScanner.nextDouble();
			}
			Utils.printOnScreen("Kоличество процессоров: " + processorsCount + "\n");
		}
		Utils.printOnScreen("\nПринятие решения..\n");

		// Init context
		Context.getInstance().setParameter(Constants.Parameters.ARRAY_LENGTH, arrayLength);
		Context.getInstance().setParameter(Constants.Parameters.PROCESSERS_COUNT, processorsCount);

		// Init FAO
		IFrameAccessObject fao = new DefaultFrameAccessObject();

		// Init Inference engine
		IInferenceEngine engine = new DefaultInferenceEngine(fao);

		ResultType result = (ResultType) engine.execute();
		processResult(result);
	}
	
	private static void processResult(ResultType result) {
		
		Utils.printOnScreen("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
		String resultMessage = "Результат: ";
		switch (result) {
		case YES:
			Utils.printOnScreen(resultMessage + "Эффективно\n");
			break;
		case NO:
			Utils.printOnScreen(resultMessage + "Не эффективно\n");
			break;
		case DO_NOT_KNOW:
			Utils.printOnScreen(resultMessage + "Система не может дать ответа\n");
			break;
		default:
			break;
		}
	}

	private static String readParameter(String parameterName) {
		
		StringBuilder text = new StringBuilder();
		String NL = " ";
		Scanner scanner = null;
		try
		{
			scanner = new Scanner(new File("./input/"+ parameterName + ".txt"));
			while (scanner.hasNextLine()) {
				text.append(scanner.nextLine() + NL);
			}
		}
		catch(FileNotFoundException e) {
			Utils.printOnScreen("file '" + parameterName + ".txt' is not found");
			logger.error("file '" + parameterName + ".txt' is not found");
			return null;
		} finally {
			if(scanner != null) {
				scanner.close();
			}
		}
		return text.toString();
	}
	
	private static Double defineArrayLenght(String[] args) {
		
		Double arrayLength;
		if (args.length > 0) {
			arrayLength = new Double(args[0]);
		} else {
			Utils.printOnScreen("Введите длину массива: ");
			arrayLength = consoleScanner.nextDouble();
		}
		Utils.printOnScreen("Длина массива (n): " + arrayLength);
		logger.info("Длина массива (n): " + arrayLength);
		
		return arrayLength;
	}
	
	private static void test() {
		double[] inputX = {1, 3, 4, 5, 7};
		double[] inputY = {1, 3, 4, 5, 3};
		double x = MathUtils.lagrange_polynomial(inputX, inputY, 2);
		x = MathUtils.lagrange_polynomial(inputX, inputY, 6);
	}
}
